/*
 * Copyright (C) 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.copybara.git.gerritapi;

import static com.google.copybara.git.gerritapi.GerritApiUtil.parseTimestamp;

import com.google.api.client.util.Key;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkValue;

/** https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info */
@SuppressWarnings("unused")
@StarlarkBuiltin(
    name = "gerritapi.ChangeInfo",
    doc = "Gerrit change information.")
public class ChangeInfo implements StarlarkValue {

  @Key private String id;

  @Key("triplet_id")
  private String tripletId;

  @Key private String project;
  @Key private String branch;
  @Key private String topic;
  @Key("change_id") private String changeId;
  @Key private String subject;
  @Key private String status;
  @Key private String created;
  @Key private String updated;
  @Key private String submitted;
  @Key private boolean submittable;

  @Key("work_in_progress")
  private boolean workInProgress;

  @Key("_number") private long number;
  @Key private AccountInfo owner;

  @Key("submit_requirements")
  private List<SubmitRequirementResultInfo> submitRequirements;

  @Key private Map<String, LabelInfo> labels;
  @Key private List<ChangeMessageInfo> messages;
  @Key("current_revision") private String currentRevision;
  @Key("revisions") private Map<String, RevisionInfo> allRevisions;
  @Key("_more_changes") private boolean moreChanges;
  @Key private Map<String, List<AccountInfo>> reviewers;

  @StarlarkMethod(
      name = "id",
      doc =
          "The ID of the change in the format \"`<project>~<branch>~<Change-Id>`\", where "
              + "'project', 'branch' and 'Change-Id' are URL encoded. For 'branch' the "
              + "refs/heads/ prefix is omitted.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getId() {
    return id;
  }

  @StarlarkMethod(
      name = "triplet_id",
      doc =
          "The ID of the change in the format \"'<project>~<branch>~<Change-Id>'\", where 'project'"
              + " and 'branch' are URL encoded. For 'branch' the refs/heads/ prefix is omitted.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getTripletId() {
    return tripletId;
  }

  @StarlarkMethod(
      name = "project",
      doc = "The name of the project.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getProject() {
    return project;
  }

  @StarlarkMethod(
      name = "branch",
      doc = "The name of the target branch.\n" + "The refs/heads/ prefix is omitted.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getBranch() {
    return branch;
  }

  @StarlarkMethod(
      name = "topic",
      doc = "The topic to which this change belongs.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getTopic() {
    return topic;
  }

  @StarlarkMethod(
      name = "change_id",
      doc = "The Change-Id of the change.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getChangeId() {
    return changeId;
  }

  @StarlarkMethod(
      name = "subject",
      doc = "The subject of the change (header line of the commit message).",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getSubject() {
    return subject;
  }

  public ChangeStatus getStatus() {
    return ChangeStatus.valueOf(status);
  }

  @StarlarkMethod(
      name = "status",
      doc = "The status of the change (NEW, MERGED, ABANDONED).",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getStatusAsString() {
    return status;
  }

  public ZonedDateTime getCreated() {
    return parseTimestamp(created);
  }

  @StarlarkMethod(
      name = "created",
      doc = "The timestamp of when the change was created.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getCreatedForSkylark() {
    return created;
  }

  public ZonedDateTime getUpdated() {
    return parseTimestamp(updated);
  }

  @StarlarkMethod(
      name = "updated",
      doc = "The timestamp of when the change was last updated.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getUpdatedForSkylark() {
    return updated;
  }

  public ZonedDateTime getSubmitted() {
    return parseTimestamp(submitted);
  }

  @StarlarkMethod(
      name = "submitted",
      doc = "The timestamp of when the change was submitted.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getSubmittedForSkylark() {
    return submitted;
  }

  @StarlarkMethod(
      name = "submittable",
      doc =
          "Whether the change has been approved by the project submit rules. Only set if "
              + "requested via additional field SUBMITTABLE.",
      structField = true)
  public boolean isSubmittable() {
    return submittable;
  }

  @StarlarkMethod(
      name = "work_in_progress",
      doc = "Whether the change is marked as \"Work in progress\".",
      structField = true)
  public boolean isWorkInProgress() {
    return workInProgress;
  }

  public long getNumber() {
    return number;
  }

  @StarlarkMethod(name = "number", doc = "The legacy numeric ID of the change.", structField = true)
  public String getNumberAsString() {
    return Long.toString(number);
  }

  @StarlarkMethod(
      name = "owner",
      doc = "The owner of the change as an AccountInfo entity.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public AccountInfo getOwner() {
    return owner;
  }

  public ImmutableList<SubmitRequirementResultInfo> getSubmitRequirements() {
    return submitRequirements == null
        ? ImmutableList.of()
        : ImmutableList.copyOf(submitRequirements);
  }

  @StarlarkMethod(
      name = "submit_requirements",
      doc = "A list of the evaluated submit requirements for the change.",
      structField = true)
  public List<SubmitRequirementResultInfo> getSubmitRequirementsForSkylark() {
    return ImmutableList.copyOf(getSubmitRequirements());
  }

  public ImmutableMap<String, LabelInfo> getLabels() {
    return labels == null ? ImmutableMap.of() : ImmutableMap.copyOf(labels);
  }

  @StarlarkMethod(
      name = "labels",
      doc =
          "The labels of the change as a map that maps the label names to LabelInfo entries.\n"
              + "Only set if labels or detailed labels are requested.",
      structField = true)
  public Dict<String, LabelInfo> getLabelsForSkylark() {
    return Dict.immutableCopyOf(getLabels());
  }

  public List<ChangeMessageInfo> getMessages() {
    return messages == null ? ImmutableList.of() : ImmutableList.copyOf(messages);
  }

  @StarlarkMethod(
      name = "messages",
      doc =
          "Messages associated with the change as a list of ChangeMessageInfo entities.\n"
              + "Only set if messages are requested.",
      structField = true)
  public Sequence<ChangeMessageInfo> getMessagesForSkylark() {
    return StarlarkList.immutableCopyOf(getMessages());
  }

  @StarlarkMethod(
      name = "current_revision",
      doc =
          "The commit ID of the current patch set of this change.\n"
              + "Only set if the current revision is requested or if all revisions are requested.",
      structField = true,
      allowReturnNones = true)
  @Nullable
  public String getCurrentRevision() {
    return currentRevision;
  }

  public ImmutableMap<String, RevisionInfo> getAllRevisions() {
    return allRevisions == null ? ImmutableMap.of() : ImmutableMap.copyOf(allRevisions);
  }

  @StarlarkMethod(
      name = "revisions",
      doc =
          "All patch sets of this change as a map that maps the commit ID of the patch set to a "
              + "RevisionInfo entity.\n"
              + "Only set if the current revision is requested (in which case it will only contain "
              + "a key for the current revision) or if all revisions are requested.",
      structField = true)
  public Dict<String, RevisionInfo> getAllRevisionsForSkylark() {
    return Dict.immutableCopyOf(getAllRevisions());
  }

  public ImmutableMap<String, List<AccountInfo>> getReviewers() {
    return reviewers == null? ImmutableMap.of(): ImmutableMap.copyOf(reviewers);
  }

  public boolean isMoreChanges() {
    return moreChanges;
  }

  @Override
  public void repr(Printer printer) {
    printer.append(toString());
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("id", id)
        .add("project", project)
        .add("branch", branch)
        .add("topic", topic)
        .add("changeId", changeId)
        .add("subject", subject)
        .add("status", status)
        .add("created", created)
        .add("updated", updated)
        .add("submitted", submitted)
        .add("submittable", submittable)
        .add("work_in_progress", workInProgress)
        .add("number", number)
        .add("owner", owner)
        .add("submitRequirements", submitRequirements)
        .add("labels", labels)
        .add("messages", messages)
        .add("currentRevision", currentRevision)
        .add("allRevisions", allRevisions)
        .add("moreChanges", moreChanges)
        .toString();
  }
}
